package com.lonely.common.utils.oss;

import cn.hutool.core.io.FileUtil;
import com.lonely.common.constant.MinIoConstant;
import com.lonely.common.core.domain.oss.MinIoParamBean;
import com.lonely.common.exception.BusinessException;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.PutObjectOptions;
import io.minio.Result;
import io.minio.errors.*;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.Assert;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * minio文件上传工具类
 */
@Slf4j
public class MinIoUtil {


    /**
     * 上传文件
     *
     * @param inputStream 文件
     * @return 文件链接地址
     */
    public static String uploadFile(MinIoParamBean minIoParamBean, InputStream inputStream, String remoteFilePath) {
        return uploadFile(minIoParamBean, null, inputStream, remoteFilePath);
    }

    /**
     * 上传文件
     *
     * @param inputStream 文件
     * @return 文件链接地址
     */
    public static String uploadFile(MinIoParamBean minIoParamBean, String fileName, InputStream inputStream, String remoteFilePath) {
        String fileUrl = "";
        try {
            MinioClient minioClient = initMinio(minIoParamBean);
            // 检查存储桶是否已经存在
            if (minioClient.bucketExists(minIoParamBean.getBucketName())) {
                log.info("Bucket already exists.");
            } else {
                // 创建一个名为ota的存储桶
                minioClient.makeBucket(minIoParamBean.getBucketName());
                log.info("create a new bucket.");
            }

            // 使用putObject上传一个本地文件到存储桶中。
            PutObjectOptions putObjectOptions = new PutObjectOptions(inputStream.available(), -1);
            putObjectOptions.setContentType(getContentTypeByFileName(fileName));
            minioClient.putObject(minIoParamBean.getBucketName(), remoteFilePath, inputStream, putObjectOptions);
            inputStream.close();
            fileUrl = minioClient.getObjectUrl(minIoParamBean.getBucketName(), remoteFilePath);
        } catch (IOException |
                 RegionConflictException |
                 InvalidResponseException |
                 InvalidBucketNameException |
                 InvalidKeyException |
                 NoSuchAlgorithmException |
                 XmlParserException |
                 ErrorResponseException |
                 InternalException |
                 InsufficientDataException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(ExceptionUtils.getStackTrace(e));
        }
        return fileUrl;
    }

    /**
     * 下载文件
     */
    public static void downloadFiles(MinIoParamBean minIoParamBean, String remoteRelativePath, String localPath) {
        try {
            MinioClient minioClient = initMinio(minIoParamBean);

            Iterable<Result<Item>> results = minioClient.listObjects(minIoParamBean.getBucketName(), remoteRelativePath);
            for (Result<Item> itemResult : results) {
                InputStream inputStream = getMinioFile(minIoParamBean, itemResult.get().objectName());
                FileUtil.writeFromStream(inputStream, new File(String.format("%s/%s", localPath, itemResult.get().objectName())));
            }

        } catch (Exception e) {
            log.info("文件获取失败" + e.getMessage());
        }
    }

    /**
     * 获取文件流
     */
    public static InputStream getMinioFile(MinIoParamBean minIoParamBean, String objectName) {
        InputStream inputStream = null;
        try {
            MinioClient minioClient = initMinio(minIoParamBean);
            inputStream = minioClient.getObject(minIoParamBean.getBucketName(), objectName);
        } catch (Exception e) {
            log.info("文件获取失败" + e.getMessage());
            throw new BusinessException(String.format("文件流获取失败:%s", ExceptionUtils.getStackTrace(e)));
        }
        return inputStream;
    }

    /**
     * 获取文件流
     */
    public static List<InputStream> getMinioFiles(MinIoParamBean minIoParamBean, String directory) {
        List<InputStream> inputStream = new ArrayList<>();
        try {
            MinioClient minioClient = initMinio(minIoParamBean);
            Iterable<Result<Item>> results = minioClient.listObjects(minIoParamBean.getBucketName(), directory);
            for (Result<Item> result : results) {
                inputStream.add(getMinioFile(minIoParamBean, result.get().objectName()));
            }
        } catch (Exception e) {
            log.error("文件获取失败" + e.getMessage());

            //关闭其他流
            for (InputStream stream : inputStream) {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException ioException) {
                        log.error("关闭流异常" + ioException.getMessage());
                    }
                }
            }

            throw new BusinessException(String.format("获取文件流失败，异常原因：[%s]", ExceptionUtils.getStackTrace(e)));
        }
        return inputStream;
    }


    /**
     * 删除文件
     */
    public static void removeObject(MinIoParamBean minIoParamBean, String bucketName, String objectName) {
        try {
            MinioClient minioClient = initMinio(minIoParamBean);
            minioClient.removeObject(bucketName, objectName);
        } catch (Exception e) {
            log.info("文件删除失败" + e.getMessage());
            throw new BusinessException(String.format("文件(%s/%s)删除失败,异常原因：%s", bucketName, objectName, ExceptionUtils.getStackTrace(e)));
        }
    }

    /**
     * 获取文件外链
     */
    public static String getObjectURL(MinIoParamBean minIoParamBean, String bucketName, String objectName, Integer expires) {
        MinioClient minioClient = initMinio(minIoParamBean);
        try {
            String url = minioClient.presignedGetObject(bucketName, objectName, expires);
            return URLDecoder.decode(url, "UTF-8");
        } catch (Exception e) {
            log.info("文件路径获取失败" + e.getMessage());
        }
        return null;
    }

    /**
     * 获取文件外链(不限制过期时间)
     */
    public static String getObjectUrl(MinIoParamBean minIoParamBean, String objectName) {
        MinioClient minioClient = initMinio(minIoParamBean);
        try {
            String url = minioClient.getObjectUrl(minIoParamBean.getBucketName(), objectName);
            return URLDecoder.decode(url, "UTF-8");
        } catch (Exception e) {
            log.info("文件路径获取失败" + e.getMessage());
        }
        return null;
    }


    /**
     * 复制文件
     *
     * @param fromMinIoParamBean 来源minio配置
     * @param toMinIoParamBean   目标minio配置
     * @param fromDirectory      来源目录
     * @param toDirectory        目标目录
     */
    public static void copyFiles(MinIoParamBean fromMinIoParamBean, MinIoParamBean toMinIoParamBean,
                                 String fromDirectory, String toDirectory) {

        try {
            MinioClient minioClient = initMinio(fromMinIoParamBean);
            Iterable<Result<Item>> results = minioClient.listObjects(fromMinIoParamBean.getBucketName(), fromDirectory);
            for (Result<Item> itemResult : results) {
                InputStream inputStream = getMinioFile(fromMinIoParamBean, itemResult.get().objectName());
                uploadFile(toMinIoParamBean, inputStream, String.format("%s/%s", toDirectory == null ? "" : toDirectory,
                        itemResult.get().objectName()));
            }
        } catch (Exception e) {
            log.error("文件复制失败" + e.getMessage());
            throw new BusinessException(String.format("MinIo文件复制失败，异常原因：[%s]", ExceptionUtils.getStackTrace(e)));
        }
    }

    /**
     * 复制文件
     *
     * @param fromMinIoParamBean 来源minio配置
     * @param toMinIoParamBean   目标minio配置
     * @param fromPath           待复制的文件
     * @param toPath             新文件path
     */
    public static void copyFile(MinIoParamBean fromMinIoParamBean, MinIoParamBean toMinIoParamBean,
                                String fromPath, String toPath) {

        try {
            InputStream inputStream = MinIoUtil.getMinioFile(fromMinIoParamBean, fromPath);
            uploadFile(toMinIoParamBean, inputStream, toPath);
        } catch (Exception e) {
            log.error("文件复制失败" + e.getMessage());
            throw new BusinessException(String.format("MinIo文件复制失败，异常原因：[%s]", ExceptionUtils.getStackTrace(e)));
        }
    }

    /**
     * 验证指定文件是否存在
     *
     * @param minIoParamBean
     * @param objectName
     * @return
     */
    public static boolean checkExists(MinIoParamBean minIoParamBean, String objectName) {
        try {
            MinioClient minioClient = initMinio(minIoParamBean);
            ObjectStat objectStat = minioClient.statObject(minIoParamBean.getBucketName(), objectName);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 验证指定文件是否存在
     *
     * @param minIoParamBean
     * @param url
     * @return
     */
    public static boolean checkUrlExists(MinIoParamBean minIoParamBean, String url) {
        try {
            String objectName = getObjectNameByUrlPath(minIoParamBean, url);
            return checkExists(minIoParamBean, objectName);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 根据url获取对应的ObjectName
     *
     * @param url
     * @return
     */
    public static String getObjectNameByUrlPath(MinIoParamBean minIoParamBean, String url) {
        //根据url获取objectName
        Assert.isTrue(StringUtils.isNotBlank(url), "待验证的URL为空");
        return url.substring(getUrlHead(minIoParamBean).length());
    }


    /**
     * 初始化客户端
     */
    private static MinioClient initMinio(MinIoParamBean minIoParamBean) {
        try {
            return new MinioClient(minIoParamBean.getEndpoint(), minIoParamBean.getAccessKey(), minIoParamBean.getSecretKey());
        } catch (Exception e) {
            throw new BusinessException(String.format("创建 MinIo Client对象异常，异常原因：[%s]", ExceptionUtils.getStackTrace(e)));
        }
    }

    /**
     * 判断文件名是否带盘符，重新处理
     */
    private static String getFileName(String fileName) {
        //判断是否带有盘符信息
        // Check for Unix-style path
        int unixSep = fileName.lastIndexOf('/');
        // Check for Windows-style path
        int winSep = fileName.lastIndexOf('\\');
        // Cut off at latest possible point
        int pos = (Math.max(winSep, unixSep));
        if (pos != -1) {
            // Any sort of path separator found...
            fileName = fileName.substring(pos + 1);
        }
        //替换上传文件名字的特殊字符
        fileName = fileName.replace("=", "").replace(",", "").replace("&", "").replace("#", "");
        return fileName;
    }


    /**
     * 获取URL头信息
     *
     * @return 返回url头信息
     */
    private static String getUrlHead(MinIoParamBean minIoParamBean) {
        //从哪个位置截取
        int cutPoint = minIoParamBean.getEndpoint().lastIndexOf('/') + 1;

        //http头
        String head = minIoParamBean.getEndpoint().substring(0, cutPoint);
        if (StringUtils.isBlank(head)) {
            head = "https://";
        }

        //服务器地址信息
        //String tail = minIoParamBean.getEndpoint().substring(cutPoint);

        //返回结果
        return head + minIoParamBean.getBucketName() + "/";
    }


    /**
     * 根据文件名匹配对应的content-type
     *
     * @param fileName
     * @return
     */
    private static String getContentTypeByFileName(String fileName) {

        if (StringUtils.isBlank(fileName)) {
            return "application/octet-stream";
        }

        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
        if (MinIoConstant.BMP.equalsIgnoreCase(fileExtension)) {
            return "image/bmp";
        }
        if (MinIoConstant.GIF.equalsIgnoreCase(fileExtension)) {
            return "image/gif";
        }

        if (MinIoConstant.JPEG.equalsIgnoreCase(fileExtension) || MinIoConstant.JPG.equalsIgnoreCase(fileExtension)) {
            return "image/jpeg";
        }

        if (MinIoConstant.PNG.equalsIgnoreCase(fileExtension)) {
            return "image/png";
        }

        if (MinIoConstant.HTML.equalsIgnoreCase(fileExtension)) {
            return "text/html";
        }

        if (MinIoConstant.CSS.equalsIgnoreCase(fileExtension)) {
            return "text/css";
        }

        if (MinIoConstant.JS.equalsIgnoreCase(fileExtension)) {
            return "application/javascript";
        }

        if (MinIoConstant.TXT.equalsIgnoreCase(fileExtension)) {
            return "text/plain";
        }

        if (MinIoConstant.VSD.equalsIgnoreCase(fileExtension)) {
            return "application/vnd.visio";
        }

        if (MinIoConstant.PPT.equalsIgnoreCase(fileExtension) || MinIoConstant.PPTX.equalsIgnoreCase(fileExtension)) {
            return "application/vnd.ms-powerpoint";
        }

        if (MinIoConstant.DOC.equalsIgnoreCase(fileExtension) || MinIoConstant.DOCX.equalsIgnoreCase(fileExtension)) {
            return "application/msword";
        }

        if (MinIoConstant.XML.equalsIgnoreCase(fileExtension)) {
            return "text/xml";
        }

        if (MinIoConstant.XLS.equalsIgnoreCase(fileExtension) || MinIoConstant.XLSX.equalsIgnoreCase(fileExtension)) {
            return "application/vnd.ms-excel";
        }


        if (MinIoConstant.SVG.equalsIgnoreCase(fileExtension)) {
            return "image/svg+xml";
        }


        if (MinIoConstant.SVGZ.equalsIgnoreCase(fileExtension)) {
            return "image/svg+xml-compressed";
        }

        if (MinIoConstant.PDF.equalsIgnoreCase(fileExtension)) {
            return "application/pdf";
        }

        return "application/octet-stream";
    }


}